package nachos.userprog;

import nachos.machine.*;
import nachos.threads.*;
import nachos.userprog.*;
import java.util.*;

import java.io.EOFException;

/**
 * Encapsulates the state of a user process that is not contained in its
 * user thread (or threads). This includes its address translation state, a
 * file table, and information about the program being executed.
 *
 * <p>
 * This class is extended by other classes to support additional functionality
 * (such as additional syscalls).
 *
 * @see	nachos.vm.VMProcess
 * @see	nachos.network.NetProcess
 */
public class UserProcess {
    /**
     * Allocate a new process.
     */
    public UserProcess() {

        processID = processIDCounter;
        processIDCounter++;
        childrenProcess = new LinkedList();
        openFileDescriptor = new LinkedList();
        openFileDescriptor.addLast(UserKernel.console.openForReading());
        openFileDescriptor.addLast(UserKernel.console.openForWriting());
        for(int i = 2; i < 16; i++)
            openFileDescriptor.addLast(null);
    }
    
    /**
     * Allocate and return a new process of the correct class. The class name
     * is specified by the <tt>nachos.conf</tt> key
     * <tt>Kernel.processClassName</tt>.
     *
     * @return	a new process of the correct class.
     */
    public static UserProcess newUserProcess() {
	return (UserProcess)Lib.constructObject(Machine.getProcessClassName());
    }

    /**
     * Execute the specified program with the specified arguments. Attempts to
     * load the program, and then forks a thread to run it.
     *
     * @param	name	the name of the file containing the executable.
     * @param	args	the arguments to pass to the executable.
     * @return	<tt>true</tt> if the program was successfully executed.
     */
    public boolean execute(String name, String[] args) {

	if (!load(name, args))
	    return false;
        
        if(UserKernel.currentProcess() != null){
            
            UserKernel.currentProcess().childrenProcess.add(new UThread(this));
            UserKernel.currentProcess().childrenProcess.getLast().process.parent = ((UThread)UThread.currentThread());
            UserKernel.currentProcess().childrenProcess.getLast().setName(name).fork();
        }
        else
            new UThread(this).setName(name).fork();
	return true;
    }

    /**
     * Save the state of this process in preparation for a context switch.
     * Called by <tt>UThread.saveState()</tt>.
     */
    public void saveState() {
        
    }

    /**
     * Restore the state of this process after a context switch. Called by
     * <tt>UThread.restoreState()</tt>.
     */
    public void restoreState() {
	Machine.processor().setPageTable(pageTable);
    }

    /**
     * Read a null-terminated string from this process's virtual memory. Read
     * at most <tt>maxLength + 1</tt> bytes from the specified address, search
     * for the null terminator, and convert it to a <tt>java.lang.String</tt>,
     * without including the null terminator. If no null terminator is found,
     * returns <tt>null</tt>.
     *
     * @param	vaddr	the starting virtual address of the null-terminated
     *			string.
     * @param	maxLength	the maximum number of characters in the string,
     *				not including the null terminator.
     * @return	the string read, or <tt>null</tt> if no null terminator was
     *		found.
     */
    public String readVirtualMemoryString(int vaddr, int maxLength) {
	Lib.assertTrue(maxLength >= 0);

	byte[] bytes = new byte[maxLength+1];

	int bytesRead = readVirtualMemory(vaddr, bytes);

	for (int length=0; length<bytesRead; length++) {
	    if (bytes[length] == 0)
		return new String(bytes, 0, length);
	}

	return null;
    }

    /**
     * Transfer data from this process's virtual memory to all of the specified
     * array. Same as <tt>readVirtualMemory(vaddr, data, 0, data.length)</tt>.
     *
     * @param	vaddr	the first byte of virtual memory to read.
     * @param	data	the array where the data will be stored.
     * @return	the number of bytes successfully transferred.
     */
    public int readVirtualMemory(int vaddr, byte[] data) {
	return readVirtualMemory(vaddr, data, 0, data.length);
    }

    /**
     * Transfer data from this process's virtual memory to the specified array.
     * This method handles address translation details. This method must
     * <i>not</i> destroy the current process if an error occurs, but instead
     * should return the number of bytes successfully copied (or zero if no
     * data could be copied).
     *
     * @param	vaddr	the first byte of virtual memory to read.
     * @param	data	the array where the data will be stored.
     * @param	offset	the first byte to write in the array.
     * @param	length	the number of bytes to transfer from virtual memory to
     *			the array.
     * @return	the number of bytes successfully transferred.
     */
    public int readVirtualMemory(int vaddr, byte[] data, int offset,
				 int length) {
	Lib.assertTrue(offset >= 0 && length >= 0 && offset+length <= data.length);

	byte[] memory = Machine.processor().getMemory();

        if (!checkVirtualAddress(vaddr))
            return 0;

        int initialPage = vaddr / pageSize;
        int initialOffset = vaddr % pageSize;
        int finalPage = (vaddr + length) / pageSize;
        int finalOffset = pageSize - ((vaddr + length) % pageSize);
        int amount;
        int paddr = (pageTable[initialPage].ppn * pageSize) + initialOffset;
        if(initialPage == finalPage)
            amount = pageSize - initialOffset - finalOffset;
        else
            amount = pageSize - initialOffset;

        System.arraycopy(memory, paddr, data, offset, amount);
        offset+=amount;
        for(int i = initialPage + 1; i <= finalPage; i++){
            paddr = pageTable[i].ppn * pageSize;
            if(i == finalPage)
                amount = pageSize - finalOffset;
            else
                amount = pageSize;
            System.arraycopy(memory, paddr, data, offset, amount);
            offset+=amount;
        }

	return offset;
    }

    /**
     * Checks if a virutal address is valid or invalid.
     *
     * @param	vaddr	the first byte of virtual memory to check
     * @return	true if is a valid virtual address or false if it isn't.
     */
    public boolean checkVirtualAddress(int vaddr){
        
        if((vaddr / pageSize) <= numPages)
            return true;

        return false;
    }

    /**
     * Transfer all data from the specified array to this process's virtual
     * memory.
     * Same as <tt>writeVirtualMemory(vaddr, data, 0, data.length)</tt>.
     *
     * @param	vaddr	the first byte of virtual memory to write.
     * @param	data	the array containing the data to transfer.
     * @return	the number of bytes successfully transferred.
     */
    public int writeVirtualMemory(int vaddr, byte[] data) {
	return writeVirtualMemory(vaddr, data, 0, data.length);
    }

    /**
     * Transfer data from the specified array to this process's virtual memory.
     * This method handles address translation details. This method must
     * <i>not</i> destroy the current process if an error occurs, but instead
     * should return the number of bytes successfully copied (or zero if no
     * data could be copied).
     *
     * @param	vaddr	the first byte of virtual memory to write.
     * @param	data	the array containing the data to transfer.
     * @param	offset	the first byte to transfer from the array.
     * @param	length	the number of bytes to transfer from the array to
     *			virtual memory.
     * @return	the number of bytes successfully transferred.
     */
    public int writeVirtualMemory(int vaddr, byte[] data, int offset,
				  int length) {
	Lib.assertTrue(offset >= 0 && length >= 0 && offset+length <= data.length);

	byte[] memory = Machine.processor().getMemory();
	
	// for now, just assume that virtual addresses equal physical addresses
	if (!checkVirtualAddress(vaddr))
            return 0;

        int initialPage = vaddr / pageSize;
        int initialOffset = vaddr % pageSize;
        int finalPage = (vaddr + length) / pageSize;
        int finalOffset = pageSize - ((vaddr + length) % pageSize);
        int amount;
        int paddr = (pageTable[initialPage].ppn * pageSize) + initialOffset;
        if(initialPage == finalPage)
            amount = pageSize - initialOffset - finalOffset;
        else
            amount = pageSize - initialOffset;
        System.arraycopy(data, offset, memory, paddr, amount);
        offset+=amount;
        for(int i = initialPage + 1; i <= finalPage; i++){
            paddr = pageTable[i].ppn * pageSize;
            if(i == finalPage)
                amount = pageSize - finalOffset;
            else
                amount = pageSize;
            System.arraycopy(data, offset, memory, paddr, amount);
            offset+=amount;
        }

	return offset;
    }

    /**
     * Load the executable with the specified name into this process, and
     * prepare to pass it the specified arguments. Opens the executable, reads
     * its header information, and copies sections and arguments into this
     * process's virtual memory.
     *
     * @param	name	the name of the file containing the executable.
     * @param	args	the arguments to pass to the executable.
     * @return	<tt>true</tt> if the executable was successfully loaded.
     */
    private boolean load(String name, String[] args) {
	Lib.debug(dbgProcess, "UserProcess.load(\"" + name + "\")");
	
	OpenFile executable = ThreadedKernel.fileSystem.open(name, false);
	if (executable == null) {
	    Lib.debug(dbgProcess, "\topen failed");
	    return false;
	}

	try {
	    coff = new Coff(executable);
	}
	catch (EOFException e) {
	    executable.close();
	    Lib.debug(dbgProcess, "\tcoff load failed");
	    return false;
	}

	// make sure the sections are contiguous and start at page 0
	numPages = 0;
	for (int s=0; s<coff.getNumSections(); s++) {
	    CoffSection section = coff.getSection(s);
	    if (section.getFirstVPN() != numPages) {
		coff.close();
		Lib.debug(dbgProcess, "\tfragmented executable");
		return false;
	    }
            //System.out.println("***section name: "+section.getName()+"  fvpn:"+section.getFirstVPN()+" length: "+section.getLength());
	    numPages += section.getLength();
	}

	// make sure the argv array will fit in one page
	byte[][] argv = new byte[args.length][];
	int argsSize = 0;
	for (int i=0; i<args.length; i++) {
	    argv[i] = args[i].getBytes();
	    // 4 bytes for argv[] pointer; then string plus one for null byte
	    argsSize += 4 + argv[i].length + 1;
	}
	if (argsSize > pageSize) {
	    coff.close();
	    Lib.debug(dbgProcess, "\targuments too long");
	    return false;
	}

	// program counter initially points at the program entry point
	initialPC = coff.getEntryPoint();

	// next comes the stack; stack pointer initially points to top of it
	numPages += stackPages;
	initialSP = numPages*pageSize;

	// and finally reserve 1 page for arguments
	numPages++;

	if (!loadSections())
	    return false;

	// store arguments in last page
	int entryOffset = (numPages-1)*pageSize;
	int stringOffset = entryOffset + args.length*4;

	this.argc = args.length;
	this.argv = entryOffset;

	for (int i=0; i<argv.length; i++) {
	    byte[] stringOffsetBytes = Lib.bytesFromInt(stringOffset);
            //System.out.println("***puntero: " + entryOffset);
	    Lib.assertTrue(writeVirtualMemory(entryOffset,stringOffsetBytes) == 4);
            byte[] punteros = new byte[4];
            this.readVirtualMemory(entryOffset, punteros);
	    entryOffset += 4;
            //System.out.println("***data: " + stringOffset);
	    Lib.assertTrue(writeVirtualMemory(stringOffset, argv[i]) ==
		       argv[i].length);
	    stringOffset += argv[i].length;
	    Lib.assertTrue(writeVirtualMemory(stringOffset,new byte[] { 0 }) == 1);
	    stringOffset += 1;
	}
        
	return true;
    }

    /**
     * Allocates memory for this process, and loads the COFF sections into
     * memory. If this returns successfully, the process will definitely be
     * run (this is the last step in process initialization that can fail).
     *
     * @return	<tt>true</tt> if the sections were successfully loaded.
     */
    protected boolean loadSections() {
	if (numPages > Machine.processor().getAvailablePhysPageCount()) {
	    coff.close();
	    Lib.debug(dbgProcess, "\tinsufficient physical memory");
	    return false;
	}
        //create the page table
        pageTable = new TranslationEntry[numPages];

        for(int i = 0; i < numPages; i++)
            pageTable[i] = new TranslationEntry(i,Machine.processor().getAvailablePhysPage(), true,false,false,false);

	// load sections
	for (int s=0; s<coff.getNumSections(); s++) {
	    CoffSection section = coff.getSection(s);
	    
	    Lib.debug(dbgProcess, "\tinitializing " + section.getName()
		      + " section (" + section.getLength() + " pages)");

	    for (int i=0; i<section.getLength(); i++) {
		int vpn = section.getFirstVPN()+i;
                //load section in the specified physical pages and modify the attribute readOnly in the specified virtual page
                pageTable[vpn].readOnly = section.isReadOnly();
                section.loadPage(i, pageTable[vpn].ppn);
	    }
	}
	
	return true;
    }

    /**
     * Release any resources allocated by <tt>loadSections()</tt>.
     */
    protected void unloadSections() {
    }    

    /**
     * Initialize the processor's registers in preparation for running the
     * program loaded into this process. Set the PC register to point at the
     * start function, set the stack pointer register to point at the top of
     * the stack, set the A0 and A1 registers to argc and argv, respectively,
     * and initialize all other registers to 0.
     */
    public void initRegisters() {
        //System.out.println("se esta iniciando registros");
	Processor processor = Machine.processor();

	// by default, everything's 0
	for (int i=0; i<processor.numUserRegisters; i++)
	    processor.writeRegister(i, 0);

	// initialize PC and SP according
	processor.writeRegister(Processor.regPC, initialPC);
	processor.writeRegister(Processor.regSP, initialSP);

	// initialize the first two argument registers to argc and argv
	processor.writeRegister(Processor.regA0, argc);
	processor.writeRegister(Processor.regA1, argv);
    }

    /**
     * Handle the halt() system call. 
     */
    private int handleHalt() {

        Machine.halt();
	
	Lib.assertNotReached("Machine.halt() did not halt machine!");
	return 0;
    }

    /**
     * Handle the exit() system call.
     */
    private int handleExit(int status) {
        
        for(int i = 0; i < openFileDescriptor.size(); i++){
            if(openFileDescriptor.get(i) != null){
                openFileDescriptor.get(i).close();
                openFileDescriptor.set(i, null);
            }
        }
        for(int i = 0; i < childrenProcess.size(); i++)
            childrenProcess.get(i).process.parent = null;

        for(int i = 0; i < pageTable.length; i++)
            Machine.processor().setPhysPage(pageTable[i].ppn, true);

        UThread.currentThread().MyFinish();

	return status;
    }

    private int handleExec(int intName, int intArgc, int intArgv){

        int argc = intArgc;
        if(intArgc < 0)
            return -1;

        String name = readVirtualMemoryString(intName, 32);

        //int entryOffset = (numPages-1)*pageSize;
        int entryOffset = intArgv;
        
        String[] argv = new String[intArgc];
        byte[] pointer = new byte[4];
        int vaddr;
        for(int i = 0; i < argv.length; i++){
            this.readVirtualMemory(entryOffset, pointer);
            vaddr = Lib.bytesToInt(pointer, 0);
            argv[i] = this.readVirtualMemoryString(vaddr, 32);
            entryOffset += 4;
        }
        
        UserProcess child = new UserProcess();

        if(!child.execute(name, argv))
            return -1;
        //childrenProcess.getLast().join();
        return childrenProcess.getLast().process.processID;
    }

    private int handleJoin(int childID, int status){
        int indexOfChild = -1;
        for(int i = 0; i <= childrenProcess.size(); i++)
            if(childrenProcess.get(i).process.processID == childID){
                indexOfChild = i;
                break;
            }

        if(indexOfChild == -1){
            return -1;
        }

        childrenProcess.get(indexOfChild).join();
        
        return 1;
    }

    private int handleCreate(int intName){
        
        String name = this.readVirtualMemoryString(intName, 32);
        UserKernel.fileSystem.open(name, true);

        return 1;

    }

    private int handleOpen(int intName){

        String name = this.readVirtualMemoryString(intName, 32);
        OpenFile file = UserKernel.fileSystem.open(name, false);
        if (file == null)
            return -1;
        if(!openFileDescriptor.contains(file)){
            openFileDescriptor.set(descriptorsCounter, file);
            descriptorsCounter++;
        }

        return openFileDescriptor.indexOf(file);
    }

    private int handleRead(int index,int intBuffer, int count){
        
        byte[] buffer = new byte[count];
        int num = openFileDescriptor.get(index).read(buffer, 0, count);
        this.writeVirtualMemory(intBuffer, buffer);
        //this.readVirtualMemory(intBuffer, buffer);
        return num;
        
    }

    private int handleWrite(int index, int intBuffer, int count){
        //System.out.println("se va a imprimir algo "+index+" "+" "+intBuffer+" "+count);
        byte[] buffer = new byte[count];
        //System.out.println("intBuffer "+intBuffer+" buffer "+buffer);
        this.readVirtualMemory(intBuffer, buffer);
        //System.out.println("se va a imprimir algo "+index+" "+" "+buffer+" "+count);
        int num = openFileDescriptor.get(index).write(buffer, 0, count);
        
        return num;
    }

    private int handleClose(int index){
        openFileDescriptor.get(index).close();
        openFileDescriptor.set(index, null);
        return 1;
    }

    private int handleUnlink(int intName){
        System.out.println("entro a unlink");
        String name = this.readVirtualMemoryString(intName, 32);
        boolean exists = false;
        for(int i = 0; i < openFileDescriptor.size(); i++){
            if(openFileDescriptor.get(i) != null)
                if(openFileDescriptor.get(i).getName().equals(name)){
                    exists = true;
                    break;
                }
        }
        if(exists)
            return -1;
        System.out.println(name);
        UserKernel.fileSystem.remove(name);
        return 0;
    }

    private static final int
        syscallHalt = 0,
	syscallExit = 1,
	syscallExec = 2,
	syscallJoin = 3,
	syscallCreate = 4,
	syscallOpen = 5,
	syscallRead = 6,
	syscallWrite = 7,
	syscallClose = 8,
	syscallUnlink = 9;

    /**
     * Handle a syscall exception. Called by <tt>handleException()</tt>. The
     * <i>syscall</i> argument identifies which syscall the user executed:
     *
     * <table>
     * <tr><td>syscall#</td><td>syscall prototype</td></tr>
     * <tr><td>0</td><td><tt>void halt();</tt></td></tr>
     * <tr><td>1</td><td><tt>void exit(int status);</tt></td></tr>
     * <tr><td>2</td><td><tt>int  exec(char *name, int argc, char **argv);
     * 								</tt></td></tr>
     * <tr><td>3</td><td><tt>int  join(int pid, int *status);</tt></td></tr>
     * <tr><td>4</td><td><tt>int  creat(char *name);</tt></td></tr>
     * <tr><td>5</td><td><tt>int  open(char *name);</tt></td></tr>
     * <tr><td>6</td><td><tt>int  read(int fd, char *buffer, int size);
     *								</tt></td></tr>
     * <tr><td>7</td><td><tt>int  write(int fd, char *buffer, int size);
     *								</tt></td></tr>
     * <tr><td>8</td><td><tt>int  close(int fd);</tt></td></tr>
     * <tr><td>9</td><td><tt>int  unlink(char *name);</tt></td></tr>
     * </table>
     * 
     * @param	syscall	the syscall number.
     * @param	a0	the first syscall argument.
     * @param	a1	the second syscall argument.
     * @param	a2	the third syscall argument.
     * @param	a3	the fourth syscall argument.
     * @return	the value to be returned to the user.
     */
    public int handleSyscall(int syscall, int a0, int a1, int a2, int a3) {
	//System.out.println("*****syscall = "+ syscall);
        switch (syscall) {
            case syscallHalt:
                return handleHalt();

            case syscallExit:
                return handleExit(a0);

            case syscallExec:{
                return handleExec(a0, a1, a2);
            }
            case syscallJoin:
                return handleJoin(a0, a1);

            case syscallCreate:
                return handleCreate(a0);

            case syscallOpen:
                return handleOpen(a0);

            case syscallRead:
                return handleRead(a0, a1, a2);

            case syscallWrite:
                return handleWrite(a0, a1, a2);

            case syscallClose:
                return handleClose(a0);

            case syscallUnlink:
                return handleUnlink(a0);

            default:
                Lib.debug(dbgProcess, "Unknown syscall " + syscall);
                Lib.assertNotReached("Unknown system call!");
	}
	return 0;
    }

    /**
     * Handle a user exception. Called by
     * <tt>UserKernel.exceptionHandler()</tt>. The
     * <i>cause</i> argument identifies which exception occurred; see the
     * <tt>Processor.exceptionZZZ</tt> constants.
     *
     * @param	cause	the user exception that occurred.
     */
    public void handleException(int cause) {
	Processor processor = Machine.processor();

	switch (cause) {
	case Processor.exceptionSyscall:
	    int result = handleSyscall(processor.readRegister(Processor.regV0),
				       processor.readRegister(Processor.regA0),
				       processor.readRegister(Processor.regA1),
				       processor.readRegister(Processor.regA2),
				       processor.readRegister(Processor.regA3)
				       );
	    processor.writeRegister(Processor.regV0, result);
	    processor.advancePC();
	    break;					       
	default:
	    Lib.debug(dbgProcess, "Unexpected exception: " +
		      Processor.exceptionNames[cause]);
	    Lib.assertNotReached("Unexpected exception");
	}
    }

    /** The program being run by this process. */
    protected Coff coff;

    /** This process's page table. */
    protected TranslationEntry[] pageTable;
    /** The number of contiguous pages occupied by the program. */
    protected int numPages;

    /** The number of pages in the program's stack. */
    protected final int stackPages = 8;
    
    private int initialPC, initialSP;
    private int argc, argv;
	
    private static final int pageSize = Processor.pageSize;
    private static final char dbgProcess = 'a';

    
    public LinkedList<UThread> childrenProcess;
    private static int processIDCounter = 0;
    private static int descriptorsCounter = 2;
    public int processID;
    public UThread parent;
    public LinkedList<OpenFile> openFileDescriptor;
    public static boolean bandera = false;
}